Skip to content

Add support for Lambda Response Streaming#2288

Open
normj wants to merge 46 commits intofeature/response-streamingfrom
normj/response-streaming
Open

Add support for Lambda Response Streaming#2288
normj wants to merge 46 commits intofeature/response-streamingfrom
normj/response-streaming

Conversation

@normj
Copy link
Copy Markdown
Member

@normj normj commented Feb 19, 2026

Issue #, if available:
#1635

Description of changes:
Integrate Lambda response streaming support into the Amazon.Lambda.RuntimeSupport.

A hello world example of using response streaming. In this case sense I wrapped the Stream returned from CreateStream in a StreamWriter the writes will be buffered in StreamWriter till the buffer is full. I call the flush method every 10 iterations to force sending data back to the client.

using Amazon.Lambda.Core;
using Amazon.Lambda.Core.ResponseStreaming;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;

#pragma warning disable CA2252

// The function handler that will be called for each Lambda event
var handler = async (string input, ILambdaContext context) =>
{
    using var responseStream = LambdaResponseStreamFactory.CreateStream();

    using var writer = new StreamWriter(responseStream);
    for (var i = 1; i <= 100; i++)
    {
        var message = $"Hello {input} - {i}";
        await writer.WriteLineAsync(message);

        if (i % 10 == 0)
        {
            await writer.FlushAsync();
            await Task.Delay(1000);
        }
    }
};

// Build the Lambda runtime client passing in the handler to call for each
// event and the JSON serializer to use for translating Lambda JSON documents
// to .NET types.
await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer())
        .Build()
        .RunAsync();

For a use with API Gateway or Lambda Function URL you need to create the stream with the CreateHttpStream passing the status code and response headers.

using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Amazon.Lambda.Core.ResponseStreaming;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using System.Net;

#pragma warning disable CA2252

// The function handler that will be called for each Lambda event
var handler = async (APIGatewayProxyRequest request, ILambdaContext context) =>
{
    var prelude = new HttpResponseStreamPrelude
    {
        StatusCode = HttpStatusCode.OK,
        Headers =
        {
            { "Content-Type", "text/plain" }
        }
    };
   
    using var responseStream = LambdaResponseStreamFactory.CreateHttpStream(prelude);

    using var writer = new StreamWriter(responseStream);
    for (var i = 1; i <= 10000000; i++)
    {
        var message = $"Hello - {i} ({responseStream.Length.ToString("N0")}) (Remaining Time: {context.RemainingTime})";
        await writer.WriteLineAsync(message);

        if (i % 100 == 0)
        {
            await writer.FlushAsync();
        }

        if (context.RemainingTime < TimeSpan.FromSeconds(5))
        {
            await writer.WriteLineAsync("Approaching Lambda timeout, stopping the stream.");
            await writer.FlushAsync();
            break;
        }
    }
};

// Build the Lambda runtime client passing in the handler to call for each
// event and the JSON serializer to use for translating Lambda JSON documents
// to .NET types.
await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer())
        .Build()
        .RunAsync();

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@normj normj marked this pull request as draft February 19, 2026 01:50
@normj normj changed the base branch from dev to feature/response-streaming March 10, 2026 23:49
@normj normj marked this pull request as ready for review March 11, 2026 18:25
@normj normj marked this pull request as draft March 11, 2026 18:26
@normj normj force-pushed the normj/response-streaming branch from 2ed63cc to d60bb93 Compare March 11, 2026 20:10
@normj normj force-pushed the normj/response-streaming branch from 96dc138 to 0bd63a9 Compare March 12, 2026 03:03
normj added 3 commits March 17, 2026 16:59
…aders with chunk encoding which wasn't supported in SocketHandler
… of Stream so that we can potentially expose more methods in the future.
@normj normj deleted the branch feature/response-streaming April 3, 2026 21:05
@normj normj closed this Apr 3, 2026
@normj normj reopened this Apr 3, 2026
@normj normj force-pushed the normj/response-streaming branch from d3dbe0c to 1a86609 Compare April 3, 2026 23:21
@normj normj marked this pull request as ready for review April 5, 2026 01:11
{
var parts = hostAndPort.Split(':');
_host = parts[0];
_port = parts.Length > 1 ? int.Parse(parts[1], CultureInfo.InvariantCulture) : 80;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why default to 80 over 443? just because its http and not https?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reality is there should always be a port defined in hostAndPort. Change the code to not have a default and throw an error if port is not set.

// Check if we've received the complete response (ends with \r\n\r\n for headers,
// or we've read the content-length worth of body)
var text = responseText.ToString();
if (text.Contains("\r\n\r\n"))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: make constant since its used multiple times

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

if (totalRead > 16384)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats 16384 here? can you add a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comment but the value is an arbitrary safety net value.


[CollectionDefinition("Integration Tests")]
public class IntegrationTestCollection : ICollectionFixture<IntegrationTestFixture>
[CollectionDefinition("Integration Tests", DisableParallelization = true)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we have to disable parallelization?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was probably dealing with some test instability. I removed the flag and tests were successful form. See if the GitHub checks are successful with the flag removed.


namespace Amazon.Lambda.Core.ResponseStreaming
{
/// <summary>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could you give more context as to why the prelude is needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

public class HttpResponseStreamPrelude
{
/// <summary>
/// The Http status code to include in the response prelude.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: all docs for the fields of this class has "to include in the response prelude.". It feels redundant.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

Comment on lines +26 to +31
public IDictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();

/// <summary>
/// The response headers to include in the response prelude. This collection supports setting multiple values for the same headers.
/// </summary>
public IDictionary<string, IList<string>> MultiValueHeaders { get; set; } = new Dictionary<string, IList<string>>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason why both are needed? why not just have 1 that supports multi value?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For API Gateway REST APIs the JSON for the prelude must use MultiValueHeaders but Lambda URLs you have to use Headers. The properties here are 1 to 1 mirror of the union of the specs for API Gateway Rest APIs and Function URLs. If API Gateway HTTP APIs ever support they would probably require the Headers collection. At the point of generating the JSON via the ToByteArray we don't have the context to say what is the event source. It is up to the user to know which one to use.

I updated the docs for the properties to call out when to use the properties.

/// <exception cref="InvalidOperationException">Thrown if the stream is already completed or an error has been reported.</exception>
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default);


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove extra whitespace

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// </summary>
long BytesWritten { get; }


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove extra whitespace

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// <param name="responseStream">The response stream that provides data and error state.</param>
/// <param name="userAgent">The User-Agent header value.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public async Task SendStreamingResponseAsync(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be measuring the performance of this HTTP client against the default one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default HTTP client doesn't work for this scenario hence the reason we have to use our custom implementation.


// The wait time is a sanity timeout to avoid waiting indefinitely if SetHttpOutputStreamAsync is not called or takes too long to call.
// Reality is that SetHttpOutputStreamAsync should be called very quickly after CreateStream, so this timeout is generous to avoid false positives but still protects against hanging indefinitely.
private readonly static TimeSpan _httpStreamWaitTimeout = TimeSpan.FromSeconds(30);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to make this customizable by the user?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is just a sanity check but it should never be reached unless we have a bug. Plus this is more of an implementation detail that users won't understand and by exposing a config it makes this implementation detail part of our public API contract.

GarrettBeatty and others added 14 commits April 8, 2026 13:10
* release_2026-04-08

* Update test app CloudFormation templates

* Updated changelog
* Add [S3Event] annotation attribute and source generator support

- S3EventAttribute with Bucket (required), ResourceName, Events, FilterPrefix, FilterSuffix, Enabled
- S3EventAttributeBuilder for Roslyn AttributeData parsing
- TypeFullNames constants and Events hashset registration
- SyntaxReceiver secondary attribute registration
- EventTypeBuilder S3 event type mapping
- AttributeModelBuilder S3 branch
- CloudFormationWriter ProcessS3Attribute (SAM S3 event with Ref, Events list, Filter rules)
- LambdaFunctionValidator ValidateS3Events (params, return type, dependency check)
- DiagnosticDescriptors InvalidS3EventAttribute (AWSLambda0133)

Add S3Event annotation tests

- ValidS3Events.cs.txt test source with 3 test functions
- S3EventsTests.cs CloudFormation writer tests (attribute application + property sync)
- S3Events project references in TestServerlessApp.csproj and test project

IT test

PR comments

change file

fixes

PR comments

* add header
* treat warnings as errors

* Change file
* chore: improve test flakiness

* update changelog
…enerator (#2324)

* Phase 1: Add FunctionUrlAttribute with source generator wiring and CloudFormation FunctionUrlConfig generation

- New FunctionUrlAttribute class with AuthType property (NONE/AWS_IAM)
- New FunctionUrlAuthType enum
- Source generator detects FunctionUrlAttribute and maps to EventType.API
- Generated wrapper uses HttpApi V2 request/response types (same payload format)
- CloudFormationWriter emits FunctionUrlConfig on the function resource
- Dependency validation checks for Amazon.Lambda.APIGatewayEvents
- SyntaxReceiver detects missing [LambdaFunction] on [FunctionUrl] methods
- 6 new unit tests for CloudFormation template generation (JSON + YAML)

Phase 2: Add CORS support to FunctionUrlAttribute

- AllowOrigins, AllowMethods, AllowHeaders, ExposeHeaders, AllowCredentials, MaxAge properties
- FunctionUrlAttributeBuilder parses all CORS properties from AttributeData
- CloudFormationWriter emits Cors block under FunctionUrlConfig only when CORS properties are set
- 4 new unit tests for CORS generation and no-CORS scenarios

Phase 3: FunctionUrlConfig orphan cleanup and attribute switching

- Remove FunctionUrlConfig from template when [FunctionUrl] attribute is removed
- Clean transition when switching from [FunctionUrl] to [HttpApi] or [RestApi]
- 4 new unit tests for orphan cleanup and attribute switching scenarios

Phase 4: End-to-end source generator test for FunctionUrl

- FunctionUrlExample.cs test source with [FunctionUrl] + [FromQuery] + IHttpResult
- Generated wrapper snapshot using HttpApi V2 payload format
- Serverless template snapshot with FunctionUrlConfig
- Full Roslyn source generator verification test

IT tests

Update Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/FunctionUrlAttributeBuilder.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

copilot comments

change file

fix cleanup

add header

PR comments

* fix template

* fix test
* release_2026-04-14

* Update test app CloudFormation templates

* Updated changelog
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants